using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

using AMC.SubstitutionCache.Models;
using AMC.SubstitutionCache.Rendering;
using AMC.SubstitutionCache.ViewModel;

using Orchard;
using Orchard.ContentManagement;
using Orchard.Environment.Configuration;

namespace AMC.SubstitutionCache.Services
{
    public class DefaultSubstitutionCacheStorageProvider : ISubstitutionCacheStorageProvider
    {
        private readonly string _tenantName;
        private readonly WorkContext _workContext;

        public DefaultSubstitutionCacheStorageProvider(IWorkContextAccessor workContextAccessor, ShellSettings shellSettings)
        {
            _workContext = workContextAccessor.GetContext();
            _tenantName = shellSettings.Name;
        }

        public void Set(string key, SubstitutionCacheItem cacheItem) {
            _workContext.HttpContext.Cache.Add(
                key,
                cacheItem,
                null,
                cacheItem.ValidUntilUtc,
                System.Web.Caching.Cache.NoSlidingExpiration,
                System.Web.Caching.CacheItemPriority.Normal,
                null);
        }

        public void Remove(string key) {
            _workContext.HttpContext.Cache.Remove(key);
        }

        public void RemoveByTag(string tag)
        {
            while (true)
            {
                var items = _workContext.HttpContext.Cache.AsParallel()
                    .Cast<DictionaryEntry>()
                    .Select(x => x.Value)
                    .OfType<SubstitutionCacheItem>()
                    .Where(x => x.Tenant.Equals(_tenantName, StringComparison.OrdinalIgnoreCase)
                                && x.Tags.Contains(tag))
                    .Take(100).ToList();

                if (!items.Any()) 
                    break;

                foreach (var item in items)
                    Remove(item.CacheKey);
            }
        }

        public void RemoveByPart(ContentPart part)
        {
            var tag = ShapeHelper.GetPartTag(part);

            RemoveByTag(tag);
        }

        public void RemoveAll() {
            var items = GetCacheItems(0, 100).ToList();
            while (items.Any()) {
                foreach (var item in items) {
                    Remove(item.CacheKey);
                }
                items = GetCacheItems(0, 100).ToList();
            }
        }

        public SubstitutionCacheItem GetCacheItem(string key) {
            return _workContext.HttpContext.Cache.Get(key) as SubstitutionCacheItem;
        }

        private IEnumerable<SubstitutionCacheItem> GetCacheItems(int skip, int count)
        {
            // the ASP.NET cache can also contain other types of items
            return _workContext.HttpContext.Cache.AsParallel()
                .Cast<DictionaryEntry>()
                .Select(x => x.Value)
                .OfType<SubstitutionCacheItem>()
                .Where(x => x.Tenant.Equals(_tenantName, StringComparison.OrdinalIgnoreCase))
                .Skip(skip)
                .Take(count);
        }

        public IEnumerable<StatisticsItemViewModel> GetStatisticsItemViewModel(int skip, int count, out int totalCount)
        {
            const int UrlPageSize = 5;

            var contexts = _workContext.HttpContext.Cache
                .Cast<DictionaryEntry>()
                .Select(x => x.Value)
                .OfType<CacheItem>()
                .Where(x => x.Tenant.Equals(_tenantName, StringComparison.OrdinalIgnoreCase))
                .SelectMany(i => i.SubstitutionContexts.Select(c => new Tuple<SubstitutionContext, CacheItem>(c, i )));

            var cacheItemsQuery = contexts
                .GroupBy(t => t.Item1, g => g, Comparer.Value)
                .Select( g =>
                {
                    var urls = g.Select(c => c.Item2.Url).OrderBy(u => u);
                    var urlCount = urls.Count();
                    return new StatisticsItemViewModel
                    {
                        Key = g.Key.Key,
                        ShapeDescription = GetShapeDescription(g.Key),
                        Urls = urls.Take(UrlPageSize).ToList(),
                        MoreUrlCount = Math.Max(0, urlCount - UrlPageSize)
                    };
                });

            totalCount = cacheItemsQuery.Count();

            var items = cacheItemsQuery.Skip(skip).Take(count).ToList();

            foreach (var item in items)
            {
                var statItem = item;
                var substitutions = _workContext.HttpContext.Cache
                    .Cast<DictionaryEntry>()
                    .Select(x => x.Value)
                    .OfType<SubstitutionCacheItem>()
                    .Where(x => x.Tenant.Equals(_tenantName, StringComparison.OrdinalIgnoreCase)
                                && x.SubstitutionKey == statItem.Key).ToList();

                item.Substitutions = substitutions.Select(
                    s => new SubstitutionItemStatsViewModel
                        {
                            CacheKey = s.CacheKey,
                            CachedOnUtc = s.CachedOnUtc,
                            ValidUntilUtc = s.ValidUntilUtc
                        });
            }

            return items;
        }

        private string GetShapeDescription(SubstitutionContext context)
        {
            var description = context.ShapeType;

            if (context.ContentId > 0) 
                description += ", Id: " + context.ContentId;

            if (!string.IsNullOrEmpty(context.DisplayType) && context.DisplayType != "Detail")
                description += ", Display Type: " + context.DisplayType;

            if (!string.IsNullOrEmpty(context.GroupId))
                description += ", GroupId: " + context.GroupId;

            return description;
        }

        public int GetCacheItemsCount() {
            return _workContext.HttpContext.Cache.AsParallel()
                .Cast<DictionaryEntry>()
                .Select(x => x.Value)
                .OfType<SubstitutionCacheItem>()
                .Count(x => x.Tenant.Equals(_tenantName, StringComparison.OrdinalIgnoreCase));
        }

        private static readonly Lazy<SubstitutionContextEqualityComparer> Comparer = new Lazy<SubstitutionContextEqualityComparer>(() => new SubstitutionContextEqualityComparer());

        private sealed class SubstitutionContextEqualityComparer : IEqualityComparer<SubstitutionContext>
        {
            public bool Equals(SubstitutionContext x, SubstitutionContext y)
            {
                if (ReferenceEquals(x, y))
                {
                    return true;
                }
                if (ReferenceEquals(x, null))
                {
                    return false;
                }
                if (ReferenceEquals(y, null))
                {
                    return false;
                }
                if (x.GetType() != y.GetType())
                {
                    return false;
                }
                return string.Equals(x.Key, y.Key);
            }

            public int GetHashCode(SubstitutionContext obj)
            {
                return obj.Key.GetHashCode();
            }
        }
    }
}